package drds.server.sqlparser.visitor;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import drds.server.sqlparser.sqlobject.SqlObject;
import drds.server.sqlparser.sqlobject.expression.$List;
import drds.server.sqlparser.sqlobject.expression.AggregateFunction;
import drds.server.sqlparser.sqlobject.expression.AllColumn;
import drds.server.sqlparser.sqlobject.expression.Between;
import drds.server.sqlparser.sqlobject.expression.Expression;
import drds.server.sqlparser.sqlobject.expression.InList;
import drds.server.sqlparser.sqlobject.expression.Not;
import drds.server.sqlparser.sqlobject.expression.Variable;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryExpression;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryOperator;
import drds.server.sqlparser.sqlobject.expression.data_type.Null;
import drds.server.sqlparser.sqlobject.expression.data_type.Text;
import drds.server.sqlparser.sqlobject.expression.data_type.number.Decimal;
import drds.server.sqlparser.sqlobject.expression.data_type.number.Int;
import drds.server.sqlparser.sqlobject.expression.name.Identifier;
import drds.server.sqlparser.sqlobject.expression.name.TableAliasName$ColumnName;
import drds.server.sqlparser.sqlobject.expression.unary.Unary;
import drds.server.sqlparser.sqlobject.statement.delete.DeleteStatement;
import drds.server.sqlparser.sqlobject.statement.insert.InsertStatement;
import drds.server.sqlparser.sqlobject.statement.insert.ValueList;
import drds.server.sqlparser.sqlobject.statement.query.GroupBy;
import drds.server.sqlparser.sqlobject.statement.query.Query;
import drds.server.sqlparser.sqlobject.statement.query.SelectItem;
import drds.server.sqlparser.sqlobject.statement.query.SelectStatement;
import drds.server.sqlparser.sqlobject.statement.query.SubQuery;
import drds.server.sqlparser.sqlobject.statement.query.order_by.OrderBy;
import drds.server.sqlparser.sqlobject.statement.query.order_by.OrderByItem;
import drds.server.sqlparser.sqlobject.statement.query.order_by.OrderByType;
import drds.server.sqlparser.sqlobject.statement.query.union_query.UnionQuery;
import drds.server.sqlparser.sqlobject.statement.query.union_query.UnionQueryTable;
import drds.server.sqlparser.sqlobject.statement.table.ExpressionTable;
import drds.server.sqlparser.sqlobject.statement.table.JoinTable;
import drds.server.sqlparser.sqlobject.statement.table.JoinTable.JoinType;
import drds.server.sqlparser.sqlobject.statement.update.UpdateSetItem;
import drds.server.sqlparser.sqlobject.statement.update.UpdateStatement;

/**
 * 
 * @author 曹正辉<330937205@qq.com>
 * 
 */
public class OutputVisitor extends VisitorAdapter implements PrintableVisitor {

	protected final Appendable appender;
	private String indent = "\t";
	private int indentCount = 0;
	private boolean prettyFormat = true;
	protected int selectListNumberOfLine = 5;

	protected boolean groupItemSingleLine = false;

	private List<Object> parameterList;

	public OutputVisitor(Appendable appender) {
		this.appender = appender;
	}

	public int getParameterListSize() {
		if (parameterList == null) {
			return 0;
		}

		return this.parameterList.size();
	}

	public List<Object> getParameterList() {
		if (parameterList == null) {
			parameterList = new ArrayList<Object>();
		}

		return parameterList;
	}

	public void setParameterList(List<Object> parameterList) {
		this.parameterList = parameterList;
	}

	public int getIndentCount() {
		return indentCount;
	}

	public Appendable getAppender() {
		return appender;
	}

	public boolean isPrettyFormat() {
		return prettyFormat;
	}

	public void setPrettyFormat(boolean prettyFormat) {
		this.prettyFormat = prettyFormat;
	}

	public void decrementIndent() {
		this.indentCount -= 1;
	}

	public void incrementIndent() {
		this.indentCount += 1;
	}

	public void print(char value) {
		try {
			this.appender.append(value);
		} catch (IOException e) {
			throw new RuntimeException("println error", e);
		}
	}

	public void print(int value) {
		print0(Integer.toString(value));
	}

	public void print(Date date) {
		SimpleDateFormat dateFormat;
		if (date instanceof java.sql.Timestamp) {
			dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		} else {
			dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		}
		print0("'" + dateFormat.format(date) + "'");
	}

	public void print(long value) {
		print0(Long.toString(value));
	}

	public void print(String text) {
		print0(text);
	}

	protected void print0(String text) {
		try {
			this.appender.append(text);
		} catch (IOException e) {
			throw new RuntimeException("println error", e);
		}
	}

	protected void printAlias(String alias) {
		if ((alias != null) && (alias.length() > 0)) {
			print(' ');
			print0(alias);
		}
	}

	protected void printAndAccept(List<? extends SqlObject> nodes, String seperator) {
		for (int i = 0, size = nodes.size(); i < size; ++i) {
			if (i != 0) {
				print0(seperator);
			}
			nodes.get(i).accept(this);
		}
	}

	protected void printSelectList(List<SelectItem> selectItemList) {
		incrementIndent();
		for (int i = 0, size = selectItemList.size(); i < size; ++i) {
			if (i != 0) {
				if (i % selectListNumberOfLine == 0) {
					println();
				}

				print0(", ");
			}

			selectItemList.get(i).accept(this);
		}
		decrementIndent();
	}

	protected void printlnAndAccept(List<? extends SqlObject> nodes, String seperator) {
		for (int i = 0, size = nodes.size(); i < size; ++i) {
			if (i != 0) {
				println(seperator);
			}

			((SqlObject) nodes.get(i)).accept(this);
		}
	}

	public void printIndent() {
		for (int i = 0; i < this.indentCount; ++i) {
			print0(this.indent);
		}
	}

	public void println() {
		if (!isPrettyFormat()) {
			print(' ');
			return;
		}

		print0("\n");
		printIndent();
	}

	public void println(String text) {
		print(text);
		println();
	}

	protected void println0(String text) {
		print0(text);
		println();
	}

	public boolean visit(Between x) {
		x.get$().accept(this);

		if (x.isNot()) {
			print0(" not between ");
		} else {
			print0(" between ");
		}

		x.getBegin().accept(this);
		print0(" and ");
		x.getEnd().accept(this);

		return false;
	}

	public boolean visit(BinaryExpression x) {
		SqlObject parent = x.getParent();
		boolean isRoot = parent instanceof Query;
		boolean isAndOr = //
		x.getBinaryOperator() == BinaryOperator.And//
				|| //
				x.getBinaryOperator() == BinaryOperator.Or;//

		if (isRoot && isAndOr) {
			incrementIndent();
		}

		List<Expression> groupList = new ArrayList<Expression>();
		Expression left = x.getLeft();
		for (;;) {
			if (left instanceof BinaryExpression && ((BinaryExpression) left).getBinaryOperator() == x.getBinaryOperator()) {
				BinaryExpression binaryLeft = (BinaryExpression) left;
				groupList.add(binaryLeft.getRight());
				left = binaryLeft.getLeft();
			} else {
				groupList.add(left);
				break;
			}
		}

		for (int i = groupList.size() - 1; i >= 0; --i) {
			Expression item = groupList.get(i);

			visitBinaryExpressionLeftSide(item, x.getBinaryOperator());

			if (isAndOr) {
				println();
			} else {
				print0(" ");
			}
			print0(x.getBinaryOperator().name);
			print0(" ");
		}

		visitBinaryExpressionRightSide(x);

		if (isRoot && isAndOr) {
			decrementIndent();
		}

		return false;
	}

	private void visitBinaryExpressionRightSide(BinaryExpression x) {
		// 从左向右依次读取数据
		if (x.getRight() instanceof BinaryExpression) {
			BinaryExpression right = (BinaryExpression) x.getRight();
			boolean isAndOr = right.getBinaryOperator() == BinaryOperator.And //
					|| //
					right.getBinaryOperator() == BinaryOperator.Or;//

			if (// 右边的表达式大于当前表达式的优先级
			right.getBinaryOperator().priority //
			>= //
			x.getBinaryOperator().priority//
			) {//
				if (isAndOr) {
					incrementIndent();
				}

				print('(');
				right.accept(this);
				print(')');

				if (isAndOr) {
					decrementIndent();
				}
			} else {
				right.accept(this);
			}
		} else {
			x.getRight().accept(this);
		}

	}

	private void visitBinaryExpressionLeftSide(//
			Expression left, BinaryOperator binaryOperator) {
		if (left instanceof BinaryExpression) {
			BinaryExpression left$BinaryExpression = (BinaryExpression) left;
			boolean left$IsAndOr = //
			left$BinaryExpression.getBinaryOperator() == BinaryOperator.And //
					|| //
					left$BinaryExpression.getBinaryOperator() == BinaryOperator.Or;//

			if (// 左边的表达式的优先级大于当前二元表达式的操作符
			left$BinaryExpression.getBinaryOperator().priority //
			> binaryOperator.priority) {//
				if (left$IsAndOr) {
					incrementIndent();
				}
				print('(');
				left.accept(this);
				print(')');

				if (left$IsAndOr) {
					decrementIndent();
				}
			} else {
				left.accept(this);
			}
		} else {
			left.accept(this);
		}
	}

	public boolean visit(Text x) {
		if (x.getText() == null) {
			print0("null");
		} else {
			print('\'');
			print0(x.getText().replaceAll("'", "''"));
			print('\'');
		}

		return false;
	}

	public boolean visit(Identifier x) {
		print0(x.getName());
		return false;
	}

	public boolean visit(InList x) {
		x.getExpression().accept(this);

		if (x.isNot()) {
			print0(" not in (");
		} else {
			print0(" in (");
		}

		final List<Expression> itemList = x.getItemList();
		boolean printLn = false;
		if (itemList.size() > 5) {
			printLn = true;
			for (int i = 0, size = itemList.size(); i < size; ++i) {
				if (!(itemList.get(i) instanceof Text)) {
					printLn = false;
					break;
				}
			}
		}

		if (printLn) {
			incrementIndent();
			println();
			for (int i = 0, size = itemList.size(); i < size; ++i) {
				if (i != 0) {
					print0(", ");
					println();
				}
				itemList.get(i).accept(this);
			}
			decrementIndent();
			println();
		} else {
			printAndAccept(x.getItemList(), ", ");
		}

		print(')');
		return false;
	}

	public boolean visit(Int x) {
		return OutputVisitorUtils.visit(this, x);
	}

	public boolean visit(AggregateFunction x) {
		print0(x.getMethodName());
		print('(');

		printAndAccept(x.getArgumentList(), ", ");

		visitAggreateRest(x);

		print(')');

		return false;
	}

	protected void visitAggreateRest(AggregateFunction aggregate) {

	}

	public boolean visit(AllColumn x) {
		print('*');
		return true;
	}

	public boolean visit(Not x) {
		print0("not ");
		Expression expr = x.getExpression();

		boolean needQuote = false;

		if (expr instanceof BinaryExpression) {
			BinaryExpression binaryOpExpr = (BinaryExpression) expr;
			needQuote = binaryOpExpr.getBinaryOperator().isAndOr();
		}

		if (needQuote) {
			print('(');
		}
		expr.accept(this);

		if (needQuote) {
			print(')');
		}
		return false;
	}

	public boolean visit(Null x) {
		print0("null");
		return false;
	}

	public boolean visit(Decimal x) {
		return OutputVisitorUtils.visit(this, x);
	}

	public boolean visit(TableAliasName$ColumnName x) {
		x.getTableAliasName().accept(this);
		print('.');
		print0(x.getName());
		return false;
	}

	public boolean visit(GroupBy x) {
		int itemSize = x.getGroupByItemList().size();
		if (itemSize > 0) {
			print0("group by ");
			incrementIndent();
			for (int i = 0; i < itemSize; ++i) {
				if (i != 0) {
					if (groupItemSingleLine) {
						println(", ");
					} else {
						print(", ");
					}
				}
				x.getGroupByItemList().get(i).accept(this);
			}
			decrementIndent();
		}

		if (x.getFilter() != null) {
			println();
			print0("having ");
			x.getFilter().accept(this);
		}

		return false;
	}

	public boolean visit(SubQuery x) {
		x.getIQuery().setParent(x);

		x.getIQuery().accept(this);

		return false;
	}

	public boolean visit(Query x) {

		print0("select ");

		if (x.getQuantifier() != null) {
			print0("distinct ");
		}

		printSelectList(x.getSelectItemList());

		if (x.getFrom() != null) {
			println();
			print0("from ");
			x.getFrom().accept(this);
		}

		if (x.getWhere() != null) {
			println();
			print0("where ");
			x.getWhere().setParent(x);
			x.getWhere().accept(this);
		}

		if (x.getGroupBy() != null) {
			println();
			x.getGroupBy().accept(this);
		}

		return false;
	}

	public boolean visit(SelectItem x) {

		x.getExpression().accept(this);

		String alias = x.getAlias();
		if (alias != null) {
			print0(" as ");
			if (alias.indexOf(' ') == -1 || alias.charAt(0) == '"' || alias.charAt(0) == '\'') {
				print0(alias);
			} else {
				print('"');
				print0(alias);
				print('"');
			}
		}
		return false;
	}

	public boolean visit(OrderBy x) {
		if (x.getOrderByItemList().size() > 0) {
			print0("order by ");

			printAndAccept(x.getOrderByItemList(), ", ");
		}
		return false;
	}

	public boolean visit(OrderByItem x) {
		x.getExpression().accept(this);
		if (x.getOrderByType() != null) {
			print(' ');
			OrderByType type = x.getOrderByType();
			print0(type.name);
		}

		return false;
	}

	public boolean visit(ExpressionTable x) {
		x.getExpression().accept(this);

		if (x.getAlias() != null) {
			print(' ');
			print0(x.getAlias());
		}

		return false;
	}

	public boolean visit(SelectStatement stmt) {
		SubQuery select = stmt.getSubQuery();

		select.accept(this);

		return false;
	}

	public boolean visit(Variable x) {
		int index = x.getIndex();

		if (parameterList == null || index >= parameterList.size()) {
			print0(x.getName());
			return false;
		}

		Object param = parameterList.get(index);
		printParameter(param);
		return false;
	}

	public void printParameter(Object param) {
		if (param == null) {
			print0("null");
			return;
		}

		if (param instanceof Number) {
			print0(param.toString());
			return;
		}

		if (param instanceof String) {
			Text charExpr = new Text((String) param);
			visit(charExpr);
			return;
		}

		if (param instanceof Date) {
			print((Date) param);
			return;
		}

		print0("'" + param.getClass().getName() + "'");
	}

	public boolean visit(DeleteStatement x) {
		print0("delete from ");

		x.getTableName().accept(this);

		if (x.getWhere() != null) {
			println();
			print0("where ");
			incrementIndent();
			x.getWhere().setParent(x);
			x.getWhere().accept(this);
			decrementIndent();
		}

		return false;
	}

	public boolean visit(InsertStatement x) {
		print0("insert into ");

		x.getTableName().accept(this);

		if (x.getColumnNameList().size() > 0) {
			incrementIndent();
			println();
			print('(');
			for (int i = 0, size = x.getColumnNameList().size(); i < size; ++i) {
				if (i != 0) {
					if (i % 5 == 0) {
						println();
					}
					print0(", ");
				}
				x.getColumnNameList().get(i).accept(this);
			}
			print(')');
			decrementIndent();
		}

		if (x.getValueList() != null) {
			println();
			print0("values");
			println();
			x.getValueList().accept(this);
		}

		return false;
	}

	public boolean visit(UpdateSetItem x) {
		x.getColumnName().accept(this);
		print0(" = ");
		x.getValue().accept(this);
		return false;
	}

	public boolean visit(UpdateStatement x) {
		print0("update ");

		x.getTableName().accept(this);

		println();
		print0("set ");
		for (int i = 0, size = x.getUpdateItemList().size(); i < size; ++i) {
			if (i != 0) {
				print0(", ");
			}
			x.getUpdateItemList().get(i).accept(this);
		}

		if (x.getWhere() != null) {
			println();
			incrementIndent();
			print0("where ");
			x.getWhere().setParent(x);
			x.getWhere().accept(this);
			decrementIndent();
		}

		return false;
	}

	@Override
	public boolean visit(UnionQuery x) {
		x.getLeft().accept(this);
		println();
		print0(x.getUnionOperator().name);
		println();

		boolean needParen = false;

		if (x.getOrderBy() != null) {
			needParen = true;
		}

		if (needParen) {
			print('(');
			x.getRight().accept(this);
			print(')');
		} else {
			x.getRight().accept(this);
		}

		if (x.getOrderBy() != null) {
			println();
			x.getOrderBy().accept(this);
		}

		return false;
	}

	@Override
	public boolean visit(Unary x) {
		print0(x.getUnaryOperator().name);

		Expression expr = x.getExpression();

		switch (x.getUnaryOperator()) {
		default:
			break;
		}

		if (expr instanceof BinaryExpression) {
			print('(');
			expr.accept(this);
			print(')');
		} else if (expr instanceof Unary) {
			print('(');
			expr.accept(this);
			print(')');
		} else {
			expr.accept(this);
		}
		return false;
	}

	@Override
	public boolean visit(JoinTable x) {
		x.getLeft().accept(this);
		incrementIndent();

		println();
		printJoinType(x.getJoinType());
		print(' ');
		x.getRight().accept(this);

		if (x.getOn() != null) {
			incrementIndent();
			print0(" on ");
			x.getOn().accept(this);
			decrementIndent();
		}

		if (x.getAlias() != null) {
			print0(" as ");
			print0(x.getAlias());
		}

		decrementIndent();

		return false;
	}

	protected void printJoinType(JoinType joinType) {
		print0(joinType.name);
	}

	@Override
	public boolean visit(ValueList x) {
		print('(');
		incrementIndent();
		for (int i = 0, size = x.getValueList().size(); i < size; ++i) {
			if (i != 0) {
				if (i % 5 == 0) {
					println();
				}
				print0(", ");
			}

			Expression expr = x.getValueList().get(i);
			expr.setParent(x);
			expr.accept(this);
		}
		decrementIndent();
		print(')');
		return false;
	}

	@Override
	public boolean visit($List x) {
		print('(');
		printAndAccept(x.getItemList(), ", ");
		print(')');

		return false;
	}

	@Override
	public boolean visit(UnionQueryTable x) {
		print('(');
		incrementIndent();
		println();
		x.getUnionQuery().accept(this);
		decrementIndent();
		println();
		print(')');

		if (x.getAlias() != null) {
			print(' ');
			print0(x.getAlias());
		}

		return false;
	}

}
